from typing import Optional, Any, Sequence

from sqlalchemy import and_
from sqlalchemy.ext.asyncio.session import AsyncSession

from app.core.custom_exception import NotFoundException, ConflictException
from app.crud.base_crud import BaseCRUD
from app.models import User, Role
from app.models.base import IdType
from app.models.menu import Menu
from app.schemas.base_schema import MenuEnum, CacheKeyPrefixConst
from app.schemas.menu_schema import MenuCreate, MenuUpdate
from app.schemas.query_schema import BaseQueryIn


class MenuCRUD(BaseCRUD[Menu, MenuCreate, MenuUpdate]):
    async def get_auth_keys(self,id:IdType) -> Sequence[str]:
        cache_key=f"{CacheKeyPrefixConst.AUTH_MENUS_KEYS_CACHE_PREFIX.value}{id}"
        cache_auth_keys = await self.cache.get(cache_key)
        if cache_auth_keys is not None:
            return cache_auth_keys

        menus = await self.get_auth_menus(id)

        auth_keys = {menu.auth_key for menu in menus if menu.auth_key is not None}

        if len(auth_keys) == 0:
            return []

        await self.cache.set(cache_key, auth_keys, expire=3600)
        return list(auth_keys)

    async def get_auth_menus(self, id: IdType) -> Sequence[Menu]:
        cache_key=f"{CacheKeyPrefixConst.AUTH_MENUS_CACHE_PREFIX.value}{id}"
        cache_menus = await self.cache.get(cache_key)
        if cache_menus is not None:
            return [Menu(**menu) for menu in cache_menus] if len(cache_menus) != 0 else []

        role_criteria = and_(Role.is_delete == False, Role.is_enabled == True)
        menu_criteria = and_(Menu.is_delete == False, Menu.is_enabled == True, Menu.type != MenuEnum.btn)

        menus = (await self.context.set(User).where(User.id == id)
                .select_many(User.roles,criteria=(Role, role_criteria))
                .select_many(Role.menus,criteria=(Menu, menu_criteria))
                # 不能预先加载 children ,因为有可能这个 children 不是这个用户所拥有的
                # 需要菜单树，只能后面构建，或者前端自己构建
                # .include(Menu.children,criteria=(Menu, menu_criteria), recursion_depth=3)
                .to_list())

        # 序列化为字典
        # serialized = [ModelConverter.tree_model_to_dict(menu) for menu in menus] if len(menus) != 0 else []
        serialized = [menu.model_dump() for menu in menus] if len(menus) != 0 else []
        await self.cache.set(cache_key, serialized, expire=3600)
        return menus

    async def get_all( self,query_in: BaseQueryIn = None,) -> Sequence[Menu]:
        """
        获取所有启用的
        :param query_in: 自定义查询
        :return: 已启用的列表
        """
        results = (await self.context.dynamic_query(query_in)
                   .include(self.model.children, criteria=(self.model, self.model.is_delete == False),recursion_depth=3)
                   .to_list())

        # 获取结果中所有ID（不包含children属性中的）
        result_ids = {item.id for item in results}

        # 使用了预先加载，children同时存在于顶层和子级
        # 没有parent_id值，说明为顶级菜单
        # 父级菜单id不在当前result_ids中，也要放置于顶层菜单，用于菜单table动态搜索
        top_level = [
            item
            for item in results
            if item.parent_id is None or item.parent_id not in result_ids
        ]

        return top_level

    async def get_by_id(self, id: IdType, db_session: Optional[AsyncSession] = None) -> Optional[Menu]:
        """根据ID获取单个数据"""
        return (await self.context.where(self.model.id == id)
                .include(self.model.children, criteria=(Menu, Menu.is_delete == False), recursion_depth=3)
                .scalar_one_or_none())


    async def sortable(self,id:IdType,target_id:IdType) -> Sequence[Menu]:
        if target_id == id:
            raise ConflictException(message="目标位置与当前位置相同")
        current_menu = await self.context.where(self.model.id == id).include(self.model.parent).include(self.model.children).scalar_one_or_none()
        if current_menu is None:
            raise NotFoundException(message="当前菜单未找到")
        brother_menus = current_menu.parent.children
        if brother_menus is None or len(brother_menus) == 0:
            raise NotFoundException(message="目标菜单未找到")
        brother_ids = [item.id for item in brother_menus]
        if target_id not in brother_ids:
            raise ConflictException(message="移动位置超出了可移动范围")

        target_menu = brother_menus[brother_ids.index(target_id)]
        target_sort = target_menu.sort

        sort_range = []
        if current_menu.sort > target_menu.sort:
            # 向前移动
            sort_range = [item for item in brother_menus if target_menu.sort <= item.sort <= current_menu.sort]
            for item in sort_range:
                if item.id == current_menu.id:
                    item.sort = target_sort
                else:
                    item.sort +=1
        elif current_menu.sort < target_menu.sort:
            # 向后移动
            sort_range = [item for item in brother_menus if target_menu.sort >= item.sort >= current_menu.sort]
            for item in sort_range:
                if item.id == current_menu.id:
                    item.sort = target_sort
                else:
                    item.sort -=1
        return await self.context.update_all(entities=sort_range)

